package com.hongtech.tiny.common.service;

import org.assertj.core.util.Lists;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;

import javax.annotation.Resource;
import java.util.Collections;
import java.util.concurrent.TimeUnit;

public class RedisWatcherLockService {

    private static final Logger _log = LoggerFactory.getLogger(RedisWatcherLockService.class);

    private static final Long RELEASE_SUCCESS = 1L;

    @Autowired
    private JedisConnectionFactory jedisConnectionFactory;

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 尝试获取分布式锁
     *
     * @param lockKey     键
     * @param lockValue   值
     * @param waitTimeout 等待时间
     * @param expireTime  过期时间
     */
    public boolean lockByJedis(String lockKey, String lockValue, long waitTimeout, long expireTime) {
        long nano = System.nanoTime();
        try (RedisConnection jedisConnection = this.jedisConnectionFactory.getConnection()) {
            do {
                _log.info("begin tryGetDistributedLock lockKey: {} {}", lockKey, lockValue);
                Long result = 0L;
                Object client = jedisConnection.getNativeConnection();
                if (client instanceof Jedis) {
                    Jedis jedis = (Jedis) client;
                    result = jedis.setnx(lockKey, lockValue);
                    jedis.expire(lockKey, expireTime);
                } else if (client instanceof JedisCluster) {
                    JedisCluster jedis = (JedisCluster) client;
                    result = jedis.setnx(lockKey, lockValue);
                    jedis.expire(lockKey, expireTime);
                }
                if (result != null && result > 0L)
                    return true;
                if (waitTimeout == 0L)
                    break;
                _log.info("wait release lockKey: {} {}", lockKey, lockValue);
                Thread.sleep(300L);
            } while (System.nanoTime() - nano < TimeUnit.SECONDS.toNanos(waitTimeout));
            return false;
        } catch (Exception e) {
            _log.info("分布式锁获取锁异常 lockByJedis: ", e);
        }
        return false;
    }

    /**
     * 释放分布式锁
     *
     * @param lockKey   键
     * @param lockValue 值
     */
    public boolean unLockByJedis(String lockKey, String lockValue) {
        String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        try (RedisConnection jedisConnection = this.jedisConnectionFactory.getConnection()) {
            Object result;
            _log.info("begin releaseDistributedLock  lockKey: {} {}", lockKey, lockValue);
            Object nativeConnection = jedisConnection.getNativeConnection();
            if (nativeConnection instanceof Jedis) {
                Jedis jedis = (Jedis) nativeConnection;
                result = jedis.eval(luaScript, Collections.singletonList(lockKey), Collections.singletonList(lockValue));
            } else {
                JedisCluster jedis = (JedisCluster) nativeConnection;
                result = jedis.eval(luaScript, Collections.singletonList(lockKey), Collections.singletonList(lockValue));
            }
            if (RELEASE_SUCCESS.equals(result)) {
                return true;
            }
        } catch (Exception e) {
            _log.info("分布式锁释放锁异常 unLockByJedis: ", e);
        }
        return false;
    }

    /**
     * 获取锁
     * true：成功获取锁
     * false：本次请求没有拿到锁
     *
     * @param key        键
     * @param value      值
     * @param expireTime 过去时间
     * @return 是否成功获取锁
     */
    public Boolean lockByRedisTemplate(String key, String value, long expireTime) {
        Boolean lock = false;
        try {
            lock = redisTemplate.opsForValue().setIfAbsent(key, value, expireTime, TimeUnit.SECONDS);
        } catch (Exception e) {
            _log.info("Exception stack trace: ", e);
        }
        if (Boolean.TRUE.equals(lock)) {
            _log.info("{} 已经拿到了锁，当前时间：{}", Thread.currentThread().getName(), System.currentTimeMillis() / 1000);
        }
        return lock;
    }

    /**
     * 释放锁
     * true：成功释放
     * false：释放锁失败，也有可能是自动过期
     *
     * @param key   键
     * @param value 值
     * @return 是否成功释放锁
     */
    public boolean unLockByRedisTemplate(String key, String value) {
        Long result = -1L;
        String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(luaScript, Long.class);
        try {
            result = redisTemplate.execute(redisScript, Lists.list(key), value);
        } catch (Exception e) {
            _log.info("分布式锁释放一场 unLockByRedisTemplate: ", e);
        }
        if (RELEASE_SUCCESS.equals(result)) {
            _log.info("{} 已经释放了锁，当前时间：{} ", Thread.currentThread().getName(), System.currentTimeMillis() / 1000);
        }
        return RELEASE_SUCCESS.equals(result);
    }

}
